package com.wangjw.tuling.netty.rpc.common;

import com.wangjw.tuling.netty.rpc.client.NettyClient;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.SynchronousQueue;

/**
 * Created by wangjianwen on 2018/12/11.
 */
public class JDKInvocationHandler implements InvocationHandler {

    private NettyClient client;

    private Class<?> proxyClass;

    public JDKInvocationHandler(NettyClient client, Class<?> proxyClass) {
        this.client = client;
        this.proxyClass = proxyClass;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String methodName = method.getName();
        Class<?>[] paramTypes = method.getParameterTypes();
        if ("toString".equals(methodName) && paramTypes.length == 0) {
            return client.toString();
        } else if ("hashCode".equals(methodName) && paramTypes.length == 0) {
            return client.hashCode();
        } else if ("equals".equals(methodName) && paramTypes.length == 1) {
            Object another = args[0];
            return proxy == another || (proxy.getClass().isInstance(another) && client.equals(parseInvoker(another)));
        }

        RpcRequest rpcRequest = buildRequest(proxyClass.getName(), method, args);
        RpcResponse response = sendRpcRequest(rpcRequest);
        return response.getResult();
    }

    private static NettyClient parseInvoker(Object proxyObject) {
        InvocationHandler handler = Proxy.getInvocationHandler(proxyObject);
        if (handler instanceof JDKInvocationHandler) {
            return ((JDKInvocationHandler) handler).getProxyInvoker();
        }
        return null;
    }

    private NettyClient getProxyInvoker() {
        return client;
    }

    public Class<?> getProxyClass() {
        return proxyClass;
    }

    private RpcRequest buildRequest(String serviceName, Method method, Object[] args) {
        String id = UUID.randomUUID().toString();
        RpcRequest request = new RpcRequest();

        request.setId(id);
        request.setServiceName(serviceName);
        request.setMethodName(method.getName());
        request.setArgs(args);

        List<String> parameterTypes = new ArrayList<>();
        for (Class<?> parameterType : method.getParameterTypes()) {
            parameterTypes.add(parameterType.getName());
        }

        request.setParameterTypeNames(parameterTypes.toArray(new String[0]));
        return request;
    }

    private RpcResponse sendRpcRequest(RpcRequest request) {
        SynchronousQueue<RpcResponse> queue = new SynchronousQueue<>();
        NettyClient.putSynchronousQueue(request.getId(), queue);

        RpcResponse response = null;
        try {
            client.sendRpcRequest(request);
            response = queue.take();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return response;
    }
}
